package de.endrullis.idea.postfixtemplatesgenerator

import java.io.{File, PrintStream}
import java.lang.reflect.Modifier

import cn.hutool.core.io.FileUtil
import cn.hutool.core.lang.{ClassScanner, Filter}
import cn.hutool.core.util.ReUtil
import resource._

import scala.collection.JavaConverters._
import scala.io.Source

/**
 * Template file generator for the Intellij IDEA plugin "Custom Postfix Templates".
 *
 * @author Stefan Endrullis
 */
object PostfixTemplateGenerator4Hutool {

  val templateDir = new File("templates")
  val componentVersion: String = ReUtil.getGroup1("hutool-\\w+-(.*?)\\.jar;", System.getProperty("java.class.path"))
  val hutoolComponent = Set(
    "hutool-all", //  postfix 会和 子包的 有冲突,  不过可以将所有的子包的postfix放在一个文件内,方便引入
    "hutool-aop", "hutool-bloomFilter", "hutool-cache", "hutool-core",
    "hutool-cron", "hutool-crypto", "hutool-db", "hutool-dfa", "hutool-extra", "hutool-http",
    "hutool-log", "hutool-script", "hutool-setting", "hutool-system", "hutool-json", "hutool-poi",
    "hutool-captcha", "hutool-socket", "hutool-jwt")

  def main(args: Array[String]) {
    //先清空历史文件
    FileUtil.del(templateDir)

    println("[ 步骤1] 先收集hutool最新版本pom中第三方依赖包 ,再手动 reload maven project !")
    // 运行时不会 reload, 需先单独运行 步骤1(第一次会 有 ClassNotFund异常 );   或者 main运行2次 ;
    val files = DependencyCollector4Hutool.collectSourceDependency("D:\\itsoft\\workspace_idea_self\\00d-to-study\\hutool")
    DependencyCollector4Hutool.runtimeUpdate(files)

    println("[ 步骤2] 开始 生成 hutool 中工具类的 postfix 模板文件!")
    for (lang ← langList; utilsCollection ← utilsCollections) {
      val dir = new File(templateDir, lang.name)
      generateTemplateFile(dir, utilsCollection, lang)
    }

    println("[ 步骤3] 计算  已生成 hutool postfix 规则总数 ")
    PostfixTemplateCounter.main(null)

    println("[ 步骤4] 查询  有冲突的 hutool postfix")
    //hutool-all 会和子包的规则 冲突
    //PostfixTemplateOverrides.main(null)
  }

  private val utilsCollections: Set[UtilsCollection] =
    hutoolComponent.map(c =>
      UtilsCollection(
        c, // postfix 模板文件名前缀
        c + " " + componentVersion, // 文件内 描述注释
        Set.empty ++ ClassScanner.scanPackage(hutoolPackage(c), classFilter).asScala) // 扫描 cn.hutool包下 util class
    )

  private def classFilter: Filter[Class[_]] = {
    (cl: Class[_]) => cl.getName.endsWith("Util")
  }

  private def hutoolPackage(c: String) = {
    "cn." + (if (c.equals("hutool-all")) "hutool" else c.toLowerCase.replace("-", "."))
  }

  case class UtilsCollection(name: String, description: String, utilClasses: Set[Class[_]])

  val langList = List(
    Lang("java", "ARRAY", _.getCanonicalName),
    Lang("scala", "scala.Array", _.getCanonicalName.replaceFirst(".*\\.", ""))
  )

  def generateTemplateFile(dir: File, utilsCollection: UtilsCollection, lang: Lang) {
    dir.mkdirs()
    val file = new File(dir, utilsCollection.name + ".postfixTemplates")
    val out = new PrintStream(file)
    out.println(s"## Templates for ${utilsCollection.description} ##")

    for (utilClass ← utilsCollection.utilClasses) {
      out.println()
      out.println("## " + utilClass.getName.replaceFirst(".*\\.", ""))
      out.println()
      printTemplates(utilClass, lang, out)
    }
    out.close()
  }

  def printTemplates(utilClass: Class[_], lang: Lang, out: PrintStream) {
    val allMethods = utilClass.getMethods.toList.filter { m ⇒
      Modifier.isStatic(m.getModifiers) &&
        Modifier.isPublic(m.getModifiers) &&
        m.getParameterCount > 0 &&
        m.getAnnotation(classOf[Deprecated]) == null &&
        !lang.classMethodExcludes.contains(lang.mapType(m.getParameterTypes.head), m.getName)
    }
    allMethods.groupBy(_.getName).filterNot(_._1.contains("_")).foreach { case (name, methods) ⇒
      out.println("." + name + " : " + name)
      methods.groupBy(m ⇒ lang.mapType(m.getParameterTypes.head)).foreach { case (matchingType, ms) ⇒
        val params = ms.filter(_.getParameterCount > 1).map(_.getParameterTypes()(1)).toSet
        val className = utilClass.getCanonicalName
        val utilClassName = lang.utilClassName(utilClass)
        val leftSide = s"$matchingType [$className]"
        val rightSide = if (params.isEmpty) {
          s"$utilClassName.$name($$expr$$)"
        } else {
          s"$utilClassName.$name($$expr$$, $$arg$$)"
        }
        out.println(s"\t$leftSide  →  $rightSide")
      }
      out.println()
    }
  }

  case class Lang(name: String, arrayType: String, utilClassName: Class[_] ⇒ String) {
    def mapType(cls: Class[_]): String = {
      if (cls.isArray) {
        arrayType
      } else {
        cls match {
          case ShortT ⇒ "SHORT"
          case IntT ⇒ "INT"
          case LongT ⇒ "LONG"
          case FloatT ⇒ "FLOAT"
          case DoubleT ⇒ "DOUBLE"
          case CharT ⇒ "CHAR"
          case ByteT ⇒ "BYTE"
          case BooleanT ⇒ "BOOLEAN"
          case s ⇒ s.getCanonicalName
        }
      }
    }

    val classMethodExcludes: Set[(String, String)] = {
      val url = getClass.getResource(s"$name.excludes")

      if (url == null) {
        Set()
      } else {
        managed(Source.fromURL(url, "UTF-8")).acquireAndGet(_.getLines()
          .filterNot(_.isEmpty)
          .map(_.split("→") match { case Array(a, b) ⇒ (a.trim, b.trim) })
          .toSet
        )
      }
    }
  }

  private val ShortT = classOf[Short]
  private val IntT = classOf[Int]
  private val LongT = classOf[Long]
  private val FloatT = classOf[Float]
  private val DoubleT = classOf[Double]
  private val CharT = classOf[Char]
  private val ByteT = classOf[Byte]
  private val BooleanT = classOf[Boolean]

}